﻿#region License
// /* **************************************************************
// * Copyright (C) intex Consulting GmbH - All Rights Reserved    *
// *--------------------------------------------------------------*
// * Unauthorized copying of this file, via any medium is         *
// * strictly prohibited                                          *
// *--------------------------------------------------------------*
// * Proprietary and confidential                                 *
// *--------------------------------------------------------------*
// * Dissasembling/modifications strictly prohibited!             *
// ***************************************************************/
#endregion
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using ComHlp.ContentClasses.FormDef;
using ComHlp.ContentClasses.VirtualControls;
using ComHlp.ContentClasses.VirtualControls.Buttons;
using ComHlp.ContentClasses.VirtualControls.LView;
using ComHlp.enums;
using ComHlp.enums.Fields;
using ComHlp.Exceptions;
using NLog;

namespace ComHlp.Classes.Parser
{
    internal static class FormDefHelper
    {
        private static readonly Logger Log = LogManager.GetCurrentClassLogger();

        /// <summary> expand passed fields to count of repeation </summary>
        /// <param name="repeationCount"></param>
        /// <param name="listRepeationFields"></param>
        /// <param name="fdLvCont"></param>
        /// <returns></returns>
        internal static IEnumerable<ItxField> ExpandRepeationFields(int repeationCount, List<ItxField> listRepeationFields, ItxListViewContainer fdLvCont)
        {
            // return value
            List<ItxField> listFields = new List<ItxField>();

            for (int repetition = 0; repetition < repeationCount; repetition++)
            {
                // we got the second occurance of the row
                foreach (ItxField field in listRepeationFields)
                {
                    // initial settings
                    ItxField expandedField = field.Clone();
                    if (repetition != 0)
                    {
                        expandedField.FormDefName = $"{expandedField.FormDefName}_{repetition}";
                        int listViewNo = ExtractListViewNoFromConstraints(field.FieldConstraints);
                        // listviews
                        switch (field.FieldType)
                        {
                            case ItxFieldType.ListViewElement:
                                ItxListView fdLv = null;
                                if (listViewNo > -1)
                                {
                                    field.ListViewNo = listViewNo;
                                    expandedField.ListViewNo = listViewNo;

                                    fdLv = fdLvCont?.ListViews.Find(tmpFdLv => tmpFdLv.ListViewNo == listViewNo);
                                    if (fdLv != null)
                                        fdLv.RowCount++;
                                }
                                // single listview
                                else if (fdLvCont != null)
                                {
                                    // set row
                                    fdLv = fdLvCont.ListViews[0];
                                    fdLv.RowCount++;
                                }

                                // set row on base field
                                // ReSharper disable once PossibleNullReferenceException
                                expandedField.ListViewRowNumber = fdLv.RowCount - 1; //due to ++ above
                                break;
                            case ItxFieldType.ListViewVm:
                            case ItxFieldType.ListViewVm2:
                                if (listViewNo > -1)
                                {
                                    field.ListViewNo = listViewNo;
                                    expandedField.ListViewNo = listViewNo;

                                    fdLv = fdLvCont?.ListViews.Find(tmpFdLv => tmpFdLv.ListViewNo == listViewNo);
                                    if (fdLv != null)
                                    {
                                        //field.ListViewRowNumber = fdLv.RowCount - 1;
                                        expandedField.ListViewRowNumber = fdLv.RowCount;
                                    }
                                    
                                }
                                // single listview
                                else if (fdLvCont != null)
                                {
                                    // set row
                                    fdLv = fdLvCont.ListViews[0];
                                    //field.ListViewRowNumber = fdLv.RowCount - 1;
                                    expandedField.ListViewRowNumber = fdLv.RowCount;
                                }

                                break;
                            case ItxFieldType.ListViewAddition:
                                if (listViewNo > -1)
                                {
                                    field.ListViewNo = listViewNo;
                                    expandedField.ListViewNo = listViewNo;

                                    fdLv = fdLvCont?.ListViews.Find(tmpFdLv => tmpFdLv.ListViewNo == listViewNo);
                                    if (fdLv != null)
                                    {
                                        //field.ListViewRowNumber = fdLv.RowCount - 1;
                                        expandedField.ListViewRowNumber = fdLv.RowCount-1;
                                    }

                                }
                                // single listview
                                else if (fdLvCont != null)
                                {
                                    // set row
                                    fdLv = fdLvCont.ListViews[0];
                                    //field.ListViewRowNumber = fdLv.RowCount - 1;
                                    expandedField.ListViewRowNumber = fdLv.RowCount-1;
                                }
                                break;
                        }
                    }
                    else
                    {
                        /* default value is -1. due to the 2 different parsings of listview
                            (with and without expansion) we need to set it for the first field explicit to 0*/
                        expandedField.ListViewRowNumber = 0;
                    }
                    listFields.Add(expandedField);
                }
            }

            return listFields;
        }

        /// <summary> Parse the formdef header without constants! </summary>
        /// <param name="sbRecvStr"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"><paramref name="sbRecvStr"/> is <see langword="null" />.</exception>
        /// <exception cref="CommunicationException">No Header found at correct position!</exception>
        internal static FormDefHeader SplitHeader(ref StringBuilder sbRecvStr)
        {
            if (sbRecvStr == null) throw new ArgumentNullException(nameof(sbRecvStr));
            if (sbRecvStr.Length == 0) throw new ArgumentNullException(nameof(sbRecvStr));
            if (sbRecvStr[0] != 'H') throw new CommunicationException("No Header found at correct position!");

            // extract the length and convert to int
            int iLen = ParseHelper.String2Int(sbRecvStr.ToString(1, 2));

            // remove H + length (2byte)
            sbRecvStr.Remove(0, 3);

            // split header
            FormDefHeader fdHeader = new FormDefHeader
                {
                    Name = sbRecvStr.ToString(0, 8).TrimEnd(),
                    TabControlTabCount = ParseHelper.String2Int(sbRecvStr.ToString(8, 3)),
                    MapFieldCount = ParseHelper.String2Int(sbRecvStr.ToString(11, 3)),
                    FieldSelected = ParseHelper.String2Int(sbRecvStr.ToString(14, 3)),
                    FirstConstraintIndex = ParseHelper.String2Int(sbRecvStr.ToString(17, 3)),
                    LastRow = ParseHelper.String2Int(sbRecvStr.ToString(20, 3)),
                    LastColumn = ParseHelper.String2Int(sbRecvStr.ToString(23, 3)),
                    FirstRow = ParseHelper.String2Int(sbRecvStr.ToString(26, 3)),
                    FirstColumn = ParseHelper.String2Int(sbRecvStr.ToString(29, 3)),
                    MaximumFieldLength = ParseHelper.String2Int(sbRecvStr.ToString(32, 3)),
                    MaximumConstLength = ParseHelper.String2Int(sbRecvStr.ToString(35, 3)),
                    ProtocolRevision = ParseHelper.String2Int(sbRecvStr.ToString(38, 2)),
                    Language = ParseLanguage(sbRecvStr.ToString(40, 2))
                };

            // remove rest
            sbRecvStr.Remove(0, iLen);
            return fdHeader;
        }

        /// <summary> Parse the formdef field definitions </summary>
        /// <param name="sbRecvStr"></param>
        /// <param name="longestFieldLength"></param>
        /// <param name="fieldCount"></param>
        /// <returns></returns>
        /// <exception cref="ArgumentNullException"><paramref name="sbRecvStr"/> is <see langword="null" />.</exception>
        /// <exception cref="CommunicationException">Condition.</exception>
        internal static StructFieldDefinitions SplitFieldDef(ref StringBuilder sbRecvStr, int longestFieldLength, int fieldCount)
        {
            if (sbRecvStr == null) throw new ArgumentNullException(nameof(sbRecvStr));

            if (sbRecvStr[0] != 'F')
            {
#if DEBUG
                Log.Debug($"No field definitions received - will look manually: {sbRecvStr}");
#endif

                int maxCutLength = 0;
                while (sbRecvStr[0] != 'F' && maxCutLength < 10)
                {
                    sbRecvStr.Remove(0, 1);
                    maxCutLength++;
                }
                if (sbRecvStr[0] != 'F')
                    throw new CommunicationException($"No field definitions received - will look manually: {sbRecvStr}");
            }
            sbRecvStr.Remove(0, 1);

            // init
            bool repeationStart = false;
            StructFieldDefinitions sFd = new StructFieldDefinitions();
            List<ItxField> fieldList = new List<ItxField>();

            /* set the length of the field size
             * -> if the longest field is smaller than 10, field length is 1
             * -> if the longest field is smaller than 100, field length is 2
             * -> else it is 3
             */
            int fieldLength;
            if (longestFieldLength < 10)
                fieldLength = 1;
            else if (longestFieldLength < 100)
                fieldLength = 2;
            else
                fieldLength = 3;

            // parse the string 
            for (int i = 0; i < fieldCount; i++)
            {
                /* 
                 * at first, get the generall field attributes
                 * type = 1 char
                 * group = 1 char (not used currently)
                 * name = 8 chars starting from behind type */
                // ReSharper disable once UseObjectOrCollectionInitializer
                ItxField itxField = new ItxField();

                // maptype 1 char
                itxField.MapFieldType = ParseProtoclFieldType(sbRecvStr[0]);
                sbRecvStr.Remove(0, 1);

                // group 1 char
                itxField.Group = ParseMapGroup(sbRecvStr[0]);
                sbRecvStr.Remove(0, 1);

                // field name - 8 chars
                itxField.FormDefName = sbRecvStr.ToString(0, 8).TrimEnd();
                sbRecvStr.Remove(0, 8);

                // length of the field (input chars)
                itxField.Length = ParseHelper.String2Int(sbRecvStr.ToString(0, fieldLength));
                sbRecvStr.Remove(0, fieldLength);

                // get the constraint counts
                itxField.ConstraintCount = ParseHelper.String2Int(sbRecvStr.ToString(0, 2));
                sbRecvStr.Remove(0, 2);

                // attrib 1: X... = AUFBEREITUNG  - 0:NORMAL      1:PROPORTIONAL    2:FIXED FONT    
                switch (sbRecvStr[0])
                {
                    case '0':
                        itxField.Attrib1FieldFormat = ItxFieldFormat.Normal;
                        break;
                    case '1':
                        itxField.Attrib1FieldFormat = ItxFieldFormat.Proportional;
                        break;
                    case '2':
                        itxField.Attrib1FieldFormat = ItxFieldFormat.Fixed;
                        break;
                    default:
#if DEBUG
                        Log.Debug($"Invalid attribute (1) received: {sbRecvStr[0]}");
#endif
                        break;
                }
                sbRecvStr.Remove(0, 1);

                // attrib 2: .X.. = INTERNES ATTR - 0:EIN HELL    1:AUS HELL        2:EIN NORMAL  3:AUS NORMAL   4:EIN DUNKEL   5:AUS DUNKEL    
                switch (sbRecvStr[0])
                {
                    case '0':
                        itxField.Attrib2Internal = ItxFieldInternalAttribute.InputFieldBright;
                        break;
                    case '1':
                        itxField.Attrib2Internal = ItxFieldInternalAttribute.OutputFieldBrightDeactivated;
                        break;
                    case '2':
                        itxField.Attrib2Internal = ItxFieldInternalAttribute.InputFieldNormal;
                        break;
                    case '3':
                        itxField.Attrib2Internal = ItxFieldInternalAttribute.OutputFeldNormalDeactivated;
                        break;
                    case '4':
                        itxField.Attrib2Internal = ItxFieldInternalAttribute.InputFieldDark;
                        break;
                    case '5':
                        itxField.Attrib2Internal = ItxFieldInternalAttribute.OutputFieldDarkDeactivated;
                        break;
                    default:
#if DEBUG
                        Log.Debug($"Invalid attribute (2) received: {sbRecvStr[0]}");
#endif
                        break;
                }
                sbRecvStr.Remove(0, 1);

                // attrib 3: ..X. = GROSS/KLEIN   - 0:GROSS       1:KLEIN         
                switch (sbRecvStr[0])
                {
                    case '0':
                        itxField.Attrib3Casing = ItxCharacterCasing.Upper;
                        break;
                    case '1':
                        itxField.Attrib3Casing = ItxCharacterCasing.Normal;
                        break;
                    default:
#if DEBUG
                        Log.Debug($"Invalid attribute (3) received: {sbRecvStr[0]}");
#endif
                        break;
                }
                sbRecvStr.Remove(0, 1);

                // attrib 4: ...X = AUSRICHTUNG   - 0:LINKS       1:RECHTS          2:KEINE                      4:OBEN           
                switch (sbRecvStr[0])
                {
                    case '0':
                        itxField.Attrib4Alignment = ItxFieldAlignment.Left;
                        break;
                    case '1':
                        itxField.Attrib4Alignment = ItxFieldAlignment.Right;
                        break;
                    case '2':
                        itxField.Attrib4Alignment = ItxFieldAlignment.None;
                        break;
                    case '4':
                        itxField.Attrib4Alignment = ItxFieldAlignment.Top;
                        break;
                    default:
#if DEBUG
                        Log.Debug($"Invalid attribute (4) received: {sbRecvStr[0]}");
#endif
                        break;
                }
                sbRecvStr.Remove(0, 1);

                /* 
                 * further logical parsing - no more string parsing
                 */


                // passwordbox?
                if (itxField.FormDefName.Length >= 5 && itxField.FormDefName.StartsWith("PASSW"))
                    itxField.IsPasswordBox = true;

                // 
                // field repeation start
                if (itxField.MapFieldType == ItxProtocolFieldType.TableStart && itxField.FormDefName == "")
                {
                    repeationStart = true;
                    itxField.FieldType = ItxFieldType.RepeationStart;
                }
                // field repeation end
                else if (itxField.MapFieldType == ItxProtocolFieldType.TableEnd && itxField.FormDefName == "")
                {
                    repeationStart = false;
                    itxField.FieldType = ItxFieldType.RepeationEnd;
                }
                // field repeation
                else if (repeationStart)
                    itxField.FieldType = ItxFieldType.RepeationElement;

                fieldList.Add(itxField);
                sFd.ConstraintsCount = sFd.ConstraintsCount + itxField.ConstraintCount;
            }

            sFd.Fields = fieldList;
            return sFd;
        }

        /// <summary> Parse the formdef constants </summary>
        /// <param name="sbRecvStr"></param>
        /// <returns></returns>
        /// <exception cref="CommunicationException">Invalid constant length!</exception>
        internal static string SplitConstants(ref StringBuilder sbRecvStr)
        {
            if (sbRecvStr[0] != 'K')
                throw new CommunicationException("No constants received!");

            // get length
            int iLen = ParseHelper.String2Int(sbRecvStr.ToString(1, 4));
            if (iLen < 0)
                throw new CommunicationException("Invalid constant length!");

            // get the constants in a stringbuilder
            string constants;
            if (ComStatics.UseUtf8)
            {
                sbRecvStr.Remove(0, 5);
                constants = ParseHelper.ExtractUtf8(iLen, sbRecvStr, true);
            }
            else
            {
                constants = sbRecvStr.ToString(5, iLen);
                sbRecvStr.Remove(0, 5 + iLen);
            }

            return constants;
        }

        /// <summary> Parse the formdef constraints </summary>
        /// <param name="constraintCount"></param>
        /// <param name="sbRecvStr"></param>
        /// <returns></returns>
        /// <exception cref="CommunicationException">No field contraints received!</exception>
        internal static List<ItxFieldConstraint> SplitConstraints(int constraintCount, ref StringBuilder sbRecvStr)
        {
            if (sbRecvStr[0] != 'P')
                throw new CommunicationException("No field contraints received!");

            List<ItxFieldConstraint> listConstraints = new List<ItxFieldConstraint>();
            if (constraintCount > 0)
            {
                // remove leading P which indicates the start of the constraing (Pruefung)
                sbRecvStr.Remove(0, 1);

                // now parse each single constraint
                for (int j = 0; j < constraintCount; j++)
                {
                    ItxFieldConstraint constraint = new ItxFieldConstraint(sbRecvStr[0]);
                    switch (sbRecvStr[0]) //order of dm5 help
                    {
                        case '(': //check on key data type
                            constraint.ConstraintType = ItxConstraintType.KeyDataCheck;
                            constraint.Undefined = sbRecvStr.ToString(0, 7);
                            sbRecvStr.Remove(0, 7);
                            break;
                        case 'N': //numeric
                            constraint.ConstraintType = ItxConstraintType.Numeric;
                            constraint.PreComa = ParseHelper.String2Int(sbRecvStr.ToString(1, 2));
                            constraint.PostComa = ParseHelper.String2Int(sbRecvStr.ToString(3, 2));
                            constraint.Prefix = sbRecvStr[5] == '-'
                                ? ItxNumericPrefix.Negative
                                : ItxNumericPrefix.Positive;
                            constraint.Alignment = GetAlignment(sbRecvStr[6]);
                            sbRecvStr.Remove(0, 7);
                            break;
                        case 'B': //currency value
                            constraint.ConstraintType = ItxConstraintType.CurrencyValue;
                            sbRecvStr.Remove(0, 7);
                            break;
                        case 'P': //required field
                            constraint.ConstraintType = ItxConstraintType.Required;
                            sbRecvStr.Remove(0, 7);
                            break;
                        case 'D': //date
                            constraint.ConstraintType = ItxConstraintType.Date;
                            constraint.Undefined = sbRecvStr.ToString(0, 7);
                            sbRecvStr.Remove(0, 7);
                            break;
                        case 'C': //character - whatever it means
                            constraint.ConstraintType = ItxConstraintType.Character;
                            sbRecvStr.Remove(0, 7);
                            break;
                        case 'T': //translate 
                            constraint.TranslateNo = ParseHelper.String2Int(sbRecvStr.ToString(1, 4));
                            constraint.ConstraintType = constraint.TranslateNo == 0
                                ? ItxConstraintType.TranslateWithoutTable
                                : ItxConstraintType.TranslateTable;
                            sbRecvStr.Remove(0, 7);
                            break;
                        case 'G': //boundaries - whatever
                            constraint.ConstraintType = ItxConstraintType.Boundaries;
                            sbRecvStr.Remove(0, 7);
                            break;
                        case 'O': // upper boundary
                            constraint.ConstraintType = ItxConstraintType.UpperBoundary;
                            sbRecvStr.Remove(0, 13);
                            break;
                        case 'U': //lower boundary
                            constraint.ConstraintType = ItxConstraintType.LowerBoundary;
                            sbRecvStr.Remove(0, 13);
                            break;

                        case 'W':
                            // if we receive a D inside this string it is size and thickness factor
                            // example: [W10D6  ]
                            string data = sbRecvStr.ToString(0, 6).Trim();
                            if (data.Contains("D") && data.Length == 5)
                            {
                                constraint.ConstraintType = ItxConstraintType.FontWeightAndSizeFactor;
                                // parse font factor and weight
                                constraint.FontFactor = ParseHelper.String2Int(sbRecvStr.ToString(1, 2));
                                constraint.FontWeight = ParseFontWeight(ParseHelper.String2Int(sbRecvStr.ToString(4, 1)));
                            }
                            else
                            {
                                // buttons...
                                string addType = sbRecvStr.ToString(3, 4);
                                switch (addType)
                                {
                                    case "BY  ": // yes/blank - unchecked = " "
                                        constraint.ButtonType = EItxButtonType.YesCheckBox;
                                        break;
                                    case "BN  ": // no/blank - unchecked = " "
                                        constraint.ButtonType = EItxButtonType.NoCheckBox;
                                        break;
                                    case "BYN ": // yes/no - unchecked == n
                                        constraint.ButtonType = EItxButtonType.YesNoCheckBox;
                                        break;
                                    case "BP  ": // j
                                        constraint.ButtonType = EItxButtonType.ImmedPrintButton;
                                        break;
                                    case "BPN ": // mark print j/u
                                        constraint.ButtonType = EItxButtonType.MarkPrintButton;
                                        break;
                                    case "BT  ":
                                        constraint.ButtonType = EItxButtonType.TextButton;
                                        break;
                                    case "BOK ":
                                        constraint.ButtonType = EItxButtonType.OkButton;
                                        break;
                                    case "PROT":
                                        constraint.Protected = true;
                                        break;
                                    default:
#if DEBUG
                                        Log.Debug($"Unknown constraint type for (W): {addType}");
#endif
                                        break;
                                }
                            }
                            sbRecvStr.Remove(0, 7);
                            break;
                        default:
                            constraint.Undefined = sbRecvStr.ToString(0, 7);
#if DEBUG
                            Log.Debug($"Invalid constraint type received: {constraint.Undefined}");
#endif

                            sbRecvStr.Remove(0, 7);
                            break;
                    }
                    listConstraints.Add(constraint);
                }
            }
            else
                sbRecvStr.Remove(0, 1);

            return listConstraints;
        }

        /// <summary> Parse the formdef field positions </summary>
        /// <param name="sbRecvStr"></param>
        /// <param name="lastRow"></param>
        /// <param name="lastCol"></param>
        /// <param name="fieldCount"></param>
        /// <returns></returns>
        /// <exception cref="CommunicationException">No field positions received!</exception>
        internal static List<Point> SplitPositions(ref StringBuilder sbRecvStr, int lastRow, int lastCol, int fieldCount)
        {
            if (sbRecvStr[0] != 'O')
                throw new CommunicationException("No field positions received!");
            sbRecvStr.Remove(0, 1);

            int yFieldLength, xFieldLength;
            List<Point> fieldPos = new List<Point>();
            if (lastRow < 10)
                yFieldLength = 1;
            else if (lastRow < 100)
                yFieldLength = 2;
            else
                yFieldLength = 3;

            if (lastCol < 10)
                xFieldLength = 1;
            else if (lastCol < 100)
                xFieldLength = 2;
            else
                xFieldLength = 3;

            for (int i = 0; i < fieldCount; i++)
            {
                // get positions
                Point point = new Point(ParseHelper.String2Int(sbRecvStr.ToString(yFieldLength, xFieldLength)),
                    ParseHelper.String2Int(sbRecvStr.ToString(0, yFieldLength)));
                fieldPos.Add(point);
                // remove y + x
                sbRecvStr.Remove(0, yFieldLength + xFieldLength);
            }
            return fieldPos;
        }

        /// <summary> Parse the formdef field additions </summary>
        /// <param name="sbRecvStr"></param>
        /// <param name="lastRow"></param>
        /// <param name="lastColumn"></param>
        /// <param name="longestConst"></param>
        /// <param name="revision"></param>
        /// <returns></returns>
        /// <exception cref="CommunicationException">No additions received!</exception>
        internal static StructFieldAdditions SplitAdditions(ref StringBuilder sbRecvStr, int lastRow, int lastColumn, int longestConst, int revision)
        {
            if (sbRecvStr[0] != 'Z')
                throw new CommunicationException("No additions received!");

            List<ItxFieldAddition> listFieldAdd = new List<ItxFieldAddition>();
            List<ItxRectangle> listFdRectangle = new List<ItxRectangle>();

            // extract the length and convert to int
            int addLen = ParseHelper.String2Int(sbRecvStr.ToString(1, 3));
            // remove Z + length
            sbRecvStr.Remove(0, 4);

            for (int i = 0; i < addLen; i++)
            {
                switch (sbRecvStr[0])
                {
                    default:
                        ItxFieldAddition fieldAdd = ParseFieldAddition(sbRecvStr, lastRow, lastColumn, longestConst, revision);
                        listFieldAdd.Add(fieldAdd);
                        break;

                    case 'R':
                        ItxRectangle fdRectangle = ParseFormDefRectangle(sbRecvStr, revision);
                        listFdRectangle.Add(fdRectangle);
                        break;
                }
            }
            StructFieldAdditions strFldAdd = new StructFieldAdditions
                {
                    ListRectangles = listFdRectangle,
                    ListFieldAdd = listFieldAdd
                };
            return strFldAdd;
        }

        private static ItxFieldAddition ParseFieldAddition(StringBuilder sbRecvStr, int lastRow, int lastColumn, int longestConst, int revision)
        {
            int fieldNo = ParseHelper.String2Int(sbRecvStr[4] + sbRecvStr.ToString(1, 3));
            ItxFieldAddition fieldAdd = new ItxFieldAddition { FieldNo = fieldNo };
            sbRecvStr.Remove(0, 5);
            // rowNo, columnNo
            int iLen;
            if (fieldAdd.FieldNo < -1)
            {
                fieldAdd.RowNoOrPositionX = ParseHelper.String2Int(sbRecvStr.ToString(0, 4));
                fieldAdd.ColumnNo = ParseHelper.String2Int(sbRecvStr.ToString(4, 4));
                sbRecvStr.Remove(0, 8);
            }
            else
            {
                if (lastRow < 10)
                    iLen = 1;
                else if (lastRow < 100)
                    iLen = 2;
                else
                    iLen = 3;
                fieldAdd.RowNoOrPositionX = ParseHelper.String2Int(sbRecvStr.ToString(0, iLen));
                sbRecvStr.Remove(0, iLen);

                if (lastColumn < 10)
                    iLen = 1;
                else if (lastColumn < 100)
                    iLen = 2;
                else
                    iLen = 3;
                fieldAdd.ColumnNo = ParseHelper.String2Int(sbRecvStr.ToString(0, iLen));
                sbRecvStr.Remove(0, iLen);
            }
            // constant
            if (longestConst < 10)
                iLen = 1;
            else if (longestConst < 100)
                iLen = 2;
            else
                iLen = 3;
            fieldAdd.ConstLength = ParseHelper.String2Int(sbRecvStr.ToString(0, iLen));
            sbRecvStr.Remove(0, iLen);

            fieldAdd.ColorNo = ParseHelper.String2Int(sbRecvStr.ToString(0, 2));
            fieldAdd.FontNo = ParseHelper.Char2Int(sbRecvStr[2]);
            fieldAdd.FontWeight = ParseFontWeight(ParseHelper.String2Int(sbRecvStr.ToString(3,1)));
            fieldAdd.Factor = ParseHelper.String2Int(sbRecvStr.ToString(4, 3));
            fieldAdd.Alignment = GetAlignment(sbRecvStr[7]);
            sbRecvStr.Remove(0, 8);

            if (revision < 2)
                return fieldAdd;

            fieldAdd.Type = sbRecvStr[0];
            sbRecvStr.Remove(0, 1);

            return fieldAdd;
        }

        private static ItxRectangle ParseFormDefRectangle(StringBuilder sbRecvStr, int revision)
        {
            ItxRectangle itxRectangle = new ItxRectangle(ItxRectangleType.FormDef)
                {
                    StartY = ParseHelper.String2Int(sbRecvStr.ToString(1, 4)),
                    StartX = ParseHelper.String2Int(sbRecvStr.ToString(5, 4)),
                    EndY = ParseHelper.String2Int(sbRecvStr.ToString(9, 4)),
                    EndX = ParseHelper.String2Int(sbRecvStr.ToString(13, 4)),
                    ColorNo = ParseHelper.String2Int(sbRecvStr.ToString(17, 2)),
                    BorderType = sbRecvStr.ToString(17, 2)
                };
            sbRecvStr.Remove(0, 21);

            if (revision < 1)
                return itxRectangle;
            itxRectangle.Gradient = sbRecvStr.ToString(0, 2);
            sbRecvStr.Remove(0, 2);
            return itxRectangle;
        }

        /// <summary> extract the listview no from constraint AT </summary>
        /// <param name="constraints"></param>
        /// <returns></returns>
        public static int ExtractListViewNoFromConstraints(List<ItxFieldConstraint> constraints)
        {
            if (constraints.Count == 0)
                return -1;

            // got an constraint starting with AT?
            var atConstraint = constraints.FirstOrDefault(x => x.Undefined.StartsWith("AT"));
            if (atConstraint == null)
                return -1;

            int lvNo = ParseHelper.String2Int(atConstraint.Undefined.Substring(2));
            return lvNo;
        }

        /// <summary> parse protocol type </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        private static ItxProtocolFieldType ParseProtoclFieldType(char c)
        {
            switch (c)
            {
                // default to ItxProtocolFieldType.DataAreaDeactivatedWithoutHelp;
                default:
#if DEBUG
                    Log.Debug($"Invalid protocol field type: {c}");
#endif

                    return ItxProtocolFieldType.DataAreaDeactivatedWithoutHelp;
                case '1':
                    return ItxProtocolFieldType.FunctionAreaWithoutHelp;
                case '2':
                    return ItxProtocolFieldType.FunctionAreaWithHelp;
                case '3':
                    return ItxProtocolFieldType.DataAreaOpenWithoutHelp;
                case '4':
                    return ItxProtocolFieldType.DataAreaOpenWithHelp;
                case '5':
                    return ItxProtocolFieldType.DataAreaDeactivatedWithoutHelp;
                case '6':
                    return ItxProtocolFieldType.DataAreaSpecialMonitor;
                case '7':
                    return ItxProtocolFieldType.FunctionAreaOpenSpecialLength;
                case '8':
                    return ItxProtocolFieldType.DataAreaOpenSepcialLength;
                case '9':
                    return ItxProtocolFieldType.Type9;
                case 'T':
                    return ItxProtocolFieldType.TableStart;
                case 'Z':
                    return ItxProtocolFieldType.TableEnd;
            }
        }

        private static ItxServerLanguage ParseLanguage(string lang)
        {
            switch (lang.Trim())
            {
                default:
                    //case "":
                    return ItxServerLanguage.German;
                case "E":
                    return ItxServerLanguage.English;
                case "S":
                    return ItxServerLanguage.Spanish;
                case "T":
                    return ItxServerLanguage.Thai;
                case "I":
                    return ItxServerLanguage.Italian;
                case "F":
                    return ItxServerLanguage.French;
                case "P":
                    return ItxServerLanguage.Portuguese;
                case "O":
                    return ItxServerLanguage.Polish;
                case "V":
                    return ItxServerLanguage.Chiense;
                case "C":
                    return ItxServerLanguage.Czech;
            }
        }

        /// <summary> parse passed char into mapgroup enum </summary>
        /// <param name="c"></param>
        /// <returns></returns>
        private static ItxMapGroup ParseMapGroup(char c)
        {
            switch (c)
            {
                // default to C to be safe
                default:
#if DEBUG
                    Log.Debug($"Unknown map group: {c}");
#endif
                    return ItxMapGroup.DataAreaC;
                case ' ':
                    return ItxMapGroup.DataAreaC;
                case 'A':
                    return ItxMapGroup.FunctionLine;
                case 'B':
                    return ItxMapGroup.DataKey;
                case 'C':
                    return ItxMapGroup.DataAreaC;
                case 'D':
                    return ItxMapGroup.DataAreaD;
                case 'E':
                    return ItxMapGroup.DataAreaE;
                case 'F':
                    return ItxMapGroup.DataAreaF;
                case 'G':
                    return ItxMapGroup.DataAreaG;
                case 'H':
                    return ItxMapGroup.DataAreaH;
                case 'I':
                    return ItxMapGroup.DataAreaI;
                case 'J':
                    return ItxMapGroup.DataAreaJ;
                case 'K':
                    return ItxMapGroup.DataAreaK;
                case 'L':
                    return ItxMapGroup.DataAreaL;
                case 'M':
                    return ItxMapGroup.DataAreaM;
                case 'N':
                    return ItxMapGroup.DataAreaN;
                case 'O':
                    return ItxMapGroup.DataAreaO;
                case 'P':
                    return ItxMapGroup.DataAreaP;
                case 'Q':
                    return ItxMapGroup.DataAreaQ;
                case 'R':
                    return ItxMapGroup.DataAreaR;
                case 'S':
                    return ItxMapGroup.DataAreaS;
                case 'T':
                    return ItxMapGroup.DataAreaT;
                case 'U':
                    return ItxMapGroup.DataAreaU;
                case 'V':
                    return ItxMapGroup.DataAreaV;
                case 'W':
                    return ItxMapGroup.DataAreaW;
                case 'X':
                    return ItxMapGroup.DataAreaX;

                case 'a':
                    return ItxMapGroup.FunctionLine | ItxMapGroup.ProtectedHidden;
                case 'b':
                    return ItxMapGroup.DataKey | ItxMapGroup.ProtectedHidden;
                case 'c':
                    return ItxMapGroup.DataAreaC | ItxMapGroup.ProtectedHidden;
                case 'd':
                    return ItxMapGroup.DataAreaD | ItxMapGroup.ProtectedHidden;
                case 'e':
                    return ItxMapGroup.DataAreaE | ItxMapGroup.ProtectedHidden;
                case 'f':
                    return ItxMapGroup.DataAreaF | ItxMapGroup.ProtectedHidden;
                case 'g':
                    return ItxMapGroup.DataAreaG | ItxMapGroup.ProtectedHidden;
                case 'h':
                    return ItxMapGroup.DataAreaH | ItxMapGroup.ProtectedHidden;
                case 'i':
                    return ItxMapGroup.DataAreaI | ItxMapGroup.ProtectedHidden;
                case 'j':
                    return ItxMapGroup.DataAreaJ | ItxMapGroup.ProtectedHidden;
                case 'k':
                    return ItxMapGroup.DataAreaK | ItxMapGroup.ProtectedHidden;
                case 'l':
                    return ItxMapGroup.DataAreaL | ItxMapGroup.ProtectedHidden;
                case 'm':
                    return ItxMapGroup.DataAreaM | ItxMapGroup.ProtectedHidden;
                case 'n':
                    return ItxMapGroup.DataAreaN | ItxMapGroup.ProtectedHidden;
                case 'o':
                    return ItxMapGroup.DataAreaO | ItxMapGroup.ProtectedHidden;
                case 'p':
                    return ItxMapGroup.DataAreaP | ItxMapGroup.ProtectedHidden;
                case 'q':
                    return ItxMapGroup.DataAreaQ | ItxMapGroup.ProtectedHidden;
                case 'r':
                    return ItxMapGroup.DataAreaR | ItxMapGroup.ProtectedHidden;
                case 's':
                    return ItxMapGroup.DataAreaS | ItxMapGroup.ProtectedHidden;
                case 't':
                    return ItxMapGroup.DataAreaT | ItxMapGroup.ProtectedHidden;
                case 'u':
                    return ItxMapGroup.DataAreaU | ItxMapGroup.ProtectedHidden;
                case 'v':
                    return ItxMapGroup.DataAreaV | ItxMapGroup.ProtectedHidden;
                case 'w':
                    return ItxMapGroup.DataAreaW | ItxMapGroup.ProtectedHidden;
                case 'x':
                    return ItxMapGroup.DataAreaX | ItxMapGroup.ProtectedHidden;
            }
        }

        /// <summary> Converts given char to enum </summary>
        /// <param name="recv"></param>
        /// <returns></returns>
        private static ItxAlignment GetAlignment(char recv)
        {
            switch (recv)
            {
                case 'R':
                    return ItxAlignment.Right;
                case 'L':
                    return ItxAlignment.Left;
                case 'M':
                    return ItxAlignment.Middle;
                default:
                    return ItxAlignment.NotSet;
            }
        }

        /// <summary> parse the font weight </summary>
        /// <param name="itxFontWeight"></param>
        /// <returns></returns>
        private static ItxFontWeight ParseFontWeight(int itxFontWeight)
        {
            switch (itxFontWeight)
            {
                case 1:
                    return ItxFontWeight.UltraLight;
                case 2:
                    return ItxFontWeight.ExtraLight;
                case 3:
                    return ItxFontWeight.Light;
                case 4:
                    return ItxFontWeight.Thin;
                default:
                    //case 5:
                    return ItxFontWeight.Normal;
                case 6:
                    return ItxFontWeight.DemiBold;
                case 7:
                    return ItxFontWeight.Bold;
                case 8:
                    return ItxFontWeight.ExtraBold;
                case 9:
                    return ItxFontWeight.UltraBold;
            }
        }
    }
}